﻿#region Copyright (c) 2013-06, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;


namespace Amarok.Presentation
{
	/// <summary>
	/// </summary>
	[ContentProperty("Content")]
	[TemplatePart(Name = "PART_Presenter", Type = typeof(ContentPresenter))]
	public sealed class ZonePresenter : Control
	{
		// data
		private ContentPresenter mPresenter;


		#region ++ Dependency Property: Content ++

		/// <summary>
		/// The embedded control described by a zone description.
		/// </summary>
		public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
			"Content",
			typeof(ZoneDescription),
			typeof(ZonePresenter),
			new FrameworkPropertyMetadata(
				null,
				OnContentChanged,
				OnCoerceContent
			));


		/// <summary>
		/// The embedded control described by a zone description.
		/// </summary>
		[
			CustomPropertiesCategory,
			Description("The embedded control described by a zone description.")
		]
		public ZoneDescription Content
		{
			// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not 
			//            touch the getter and setter inside this dependency property!
			get
			{
				return (ZoneDescription)GetValue(ContentProperty);
			}
			set
			{
				SetValue(ContentProperty, value);
			}
		}


		private static Object OnCoerceContent(DependencyObject o, Object value)
		{
			ZonePresenter viewPresenter = o as ZonePresenter;
			if (viewPresenter != null)
				return viewPresenter.OnCoerceContent((ZoneDescription)value);
			else
				return value;
		}

		private static void OnContentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ZonePresenter viewPresenter = o as ZonePresenter;
			if (viewPresenter != null)
				viewPresenter.OnContentChanged((ZoneDescription)e.OldValue, (ZoneDescription)e.NewValue);
		}


		private ZoneDescription OnCoerceContent(ZoneDescription value)
		{
			if (value == null)
				return value;

			if (value.IsCreated)
				return (ZoneDescription)value.Clone();
			else
				return value;
		}

		private void OnContentChanged(ZoneDescription oldValue, ZoneDescription newValue)
		{
			if (base.IsLoaded == false)
				return;

			_UpdateContent(oldValue, newValue);
		}

		#endregion

		#region ++ Dependency Property: ContentDataContext ++

		/// <summary>
		/// The data context that should be applied to the embedded content.
		/// </summary>
		public static readonly DependencyProperty ContentDataContextProperty = DependencyProperty.Register(
			"ContentDataContext",
			typeof(Object),
			typeof(ZonePresenter),
			new FrameworkPropertyMetadata(
				null,
				OnContentDataContextChanged
			));


		/// <summary>
		/// The data context that should be applied to the embedded content.
		/// </summary>
		[
			CustomPropertiesCategory,
			Description("The data context that should be applied to the embedded content.")
		]
		public Object ContentDataContext
		{
			// IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not 
			//            touch the getter and setter inside this dependency property!
			get
			{
				return (Object)GetValue(ContentDataContextProperty);
			}
			set
			{
				SetValue(ContentDataContextProperty, value);
			}
		}


		private static void OnContentDataContextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			ZonePresenter viewPresenter = o as ZonePresenter;
			if (viewPresenter != null)
				viewPresenter.OnContentDataContextChanged((Object)e.NewValue);
		}


		private void OnContentDataContextChanged(Object newValue)
		{
			if (base.IsLoaded == false)
				return;

			_UpdateDataContext(newValue);
		}

		#endregion

		#region ++ Public Interface ++

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		public ZonePresenter()
		{
			base.Loaded += OnLoaded;
			base.Unloaded += OnUnloaded;
		}


		/// <summary>
		/// When overridden in a derived class, is invoked whenever application code or internal processes 
		/// call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate" />.
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			// obtain parts
			mPresenter = base.GetTemplateChild("PART_Presenter") as ContentPresenter;
		}

		#endregion

		#region ++ Public Static Interface ++

		/// <summary>
		/// </summary>
		static ZonePresenter()
		{
			DefaultStyleKeyProperty.OverrideMetadata(
				typeof(ZonePresenter),
				new FrameworkPropertyMetadata(
					typeof(ZonePresenter)
			));
		}

		#endregion

		#region Implementation

		private void OnLoaded(Object sender, RoutedEventArgs e)
		{
			// unregister from event
			base.Loaded -= OnLoaded;

			// set initial content
			_UpdateContent(null, this.Content);
		}

		private void OnUnloaded(Object sender, RoutedEventArgs e)
		{
			_UpdateContent(this.Content, null);
		}

		private void _UpdateContent(ZoneDescription oldContent, ZoneDescription newContent)
		{
			if (Designer.IsRuntimeMode)
				_UpdateContentRuntime(oldContent, newContent);
			else
				_UpdateContentDesignTime(newContent);
		}

		private void _UpdateContentDesignTime(ZoneDescription newContent)
		{
			// set-up new content
			//

			if (newContent != null)
			{
				var view = Activator.CreateInstance(newContent.ViewType, true);
				_SetContent(view);
				_UpdateDataContext(this.ContentDataContext);
			}
			else
			{
				_SetContent(new Border()
				{
					Background = Brushes.Orange,
					BorderBrush = Brushes.Red,
					BorderThickness = new Thickness(1),
				});
			}
		}

		private void _UpdateContentRuntime(ZoneDescription oldContent, ZoneDescription newContent)
		{
			// clean-up old content
			//
			if (oldContent != null)
			{
				// complete controller; don't wait for completion
				oldContent.Complete(true);
			}

			var environment = View.GetEnvironment(this);
			var options = View.GetOptions(this);

			// set-up new content
			//
			if (newContent != null && environment != null && options != null)
			{
				if (this.ContentDataContext == null)
					newContent.Create(environment, options);
				else
					newContent.Create(environment, options, this.ContentDataContext);

				_SetContent(newContent.Control);
			}
			else
			{
				// clear content
				_SetContent(null);
			}
		}

		private void _SetContent(Object content)
		{
			if (mPresenter == null)
				return;

			mPresenter.Content = content;
		}

		private void _UpdateDataContext(Object newValue)
		{
			if (mPresenter == null)
				return;

			var frameworkElement = mPresenter.Content as FrameworkElement;

			if (frameworkElement == null)
				return;

			frameworkElement.DataContext = newValue;
		}

		#endregion

	}
}
